Option Explicit

' ======================================================================
' Descriptor
' Sql 格式化的通用处理
' ======================================================================

' ======================================================================
' 生成元素
' ======================================================================

' 生成两个元素组成的 token组合
'
' @param token01 第一个元素
' @param token02 第二个元素
' @return token组合
Public Function createTwoElemTokenGroup(ByRef token01 As SqlToken, ByRef token02 As SqlToken) As SqlFormatterTokenGroup
    Set createTwoElemTokenGroup = New SqlFormatterTokenGroup
    createTwoElemTokenGroup.ISqlFormatterElem_addComponent token01
    createTwoElemTokenGroup.ISqlFormatterElem_addComponent token02
End Function

' 生成连续连接的字符串元素
'
' @return 连续连接的字符串元素
Public Function createContinuousStrElem() As SqlFormatterContinuousStrElem
    Set createContinuousStrElem = New SqlFormatterContinuousStrElem
End Function

' 将一个对象包装为连续连接的字符串元素
'
' @param e 原始元素
' @return 连续连接的字符串元素
Public Function wrapperElemToContinuousStrElem(ByRef e As ISqlFormatterElem) As SqlFormatterContinuousStrElem
    Set wrapperElemToContinuousStrElem = New SqlFormatterContinuousStrElem
    wrapperElemToContinuousStrElem.ISqlFormatterElem_addComponent e
End Function

' 生成一个逗号分隔的多个元素
'
' @param needMultiLineWhenHasComma 遇到逗号时，是否需要变成多行
'                         默认值 = false
' @return 逗号分隔的多个元素
Public Function createCommaDelimitMultiElem(Optional ByRef needMultiLineWhenHasComma As Boolean = False) As SqlFormatterCommaDeliMultiElem
    Set createCommaDelimitMultiElem = New SqlFormatterCommaDeliMultiElem

    If needMultiLineWhenHasComma Then
        createCommaDelimitMultiElem.setNeedMultiLineWhenHasComma True
    End If
End Function

' 生成一个容器作用域
'
' @return 容器作用域
Public Function createContainerEnv() As SqlFormatterContainerEnv
    Set createContainerEnv = New SqlFormatterContainerEnv
End Function

' 将一个作用域包装为容器作用域
'
' @param e 原始元素
' @return 包装后的容器作用域
Public Function wrapperEnvToContainerEnv(ByRef e As ISqlFormatterElem) As SqlFormatterContainerEnv
    Set wrapperEnvToContainerEnv = New SqlFormatterContainerEnv

    wrapperEnvToContainerEnv.ISqlFormatterElem_addComponent e
    wrapperEnvToContainerEnv.setType e.getType
End Function

' 生成一个key开始的作用域
'
' @param key
' @param t 作用域类型
' @return key开始的作用域
Public Function createKeyStartEnv(ByRef key As ISqlFormatterElem, ByVal t As SqlTokenType) As SqlFormatterKeySectionEnv
    Set createKeyStartEnv = New SqlFormatterKeySectionEnv
    createKeyStartEnv.Init key, t
End Function

' 生成一个KeySectionEnv
'
' @param key 作用域的 key
' @param other 作用域的内容
' @return KeySectionEnv 作用域对象
Public Function createKeySectionEnv(ByRef key As ISqlFormatterElem, ByRef other As ISqlFormatterElem) As SqlFormatterKeySectionEnv
    ' 1. 创建结果对象
    Set createKeySectionEnv = New SqlFormatterKeySectionEnv
    ' 2. 使用 key 初始化结果对象
    createKeySectionEnv.Init key, key.getType
    ' 3. 将 other 保存到 result
    createKeySectionEnv.ISqlFormatterElem_addComponent other
    ' 4. 退出处理，并返回 result
    ' return result
End Function

' 将一个条件包装为多条件组合
'
' @param elem 元素对象
' @return 多条件组合
Public Function wrapToMultiConditions(ByRef elem As ISqlFormatterElem) As SqlFormatterMultiConditions
    Set wrapToMultiConditions = New SqlFormatterMultiConditions
    wrapToMultiConditions.addCondition elem
End Function

' 创建一个多条件组合
'
' @return 多条件组合
Public Function createMultiConditions() As SqlFormatterMultiConditions
    Set createMultiConditions = New SqlFormatterMultiConditions
End Function

' 生成条件对象
'
' @return 包含两个元素条件对象
Public Function createCondition() As SqlFormatterTwoElemCondition
    Set createCondition = New SqlFormatterTwoElemCondition
End Function

' 生成条件对象的条件运算符左侧的内容
'
' @param firstElem 条件的第一个元素
' @param conditionOp 条件运算符
' @return 包含两个元素条件对象
Public Function createConditionOnlyOpLeft( _
    ByRef firstElem As ISqlFormatterElem, ByRef conditionOp As ISqlFormatterElem) As SqlFormatterTwoElemCondition
    Set createConditionOnlyOpLeft = New SqlFormatterTwoElemCondition
    createConditionOnlyOpLeft.ISqlFormatterElem_addComponent firstElem
    createConditionOnlyOpLeft.ISqlFormatterCondition_setConditionOperator conditionOp
End Function

' 生成一个容器列
'
' @return 容器列
Public Function createContainerCol() As SqlFormatterContainerCol
    Set createContainerCol = New SqlFormatterContainerCol
End Function

' 将一个列包装为容器列
'
' @param col 原始列对象
' @return 容器列
Public Function wrapperColToContainerCol(ByRef col As ISqlFormatterElem) As SqlFormatterContainerCol
    Set wrapperColToContainerCol = New SqlFormatterContainerCol
    wrapperColToContainerCol.ISqlFormatterElem_addComponent col
    wrapperColToContainerCol.setType col.getType
End Function

' 将一个列转换为表达式列
'
' @param col 原始列对象
' @return 表达式列
Public Function transColToExprCol(ByRef col As ISqlFormatterElem) As SqlFormatterExprCol
    ' 检查: 如果 col 是 null，则创建一个空对象，并返回
    If col Is Nothing Then
        Set transColToExprCol = New SqlFormatterExprCol
        Exit Function
    End If

    ' 检查: 如果 col 的类型是表达式列对象，则直接返回
    If Typeof col Is SqlFormatterExprCol Then
        Set transColToExprCol = col
        Exit Function
    End if

    ' 否则，创建表达式列对象，并保存 col
    Set transColToExprCol = New SqlFormatterExprCol
    transColToExprCol.ISqlFormatterElem_addComponent col
End Function

' 生成一个函数列对象
'
' @param funcName 函数名
' @return 函数列对象
Public Function createFuncCol(ByRef funcName As SqlToken) As SqlFormatterFuncCol
    Set createFuncCol = New SqlFormatterFuncCol
    createFuncCol.Init funcName
End Function

' 生成一个case列对象
'
' @param caseToken case 列对象的起始的 case token
' @return case列对象
Public Function createCaseCol(ByRef caseToken As SqlToken) As SqlFormatterCaseCol
    Set createCaseCol = New SqlFormatterCaseCol
    createCaseCol.Init caseToken
End Function

' 生成一个case的组成元素
'
' @param startToken case的组成元素的起始 token
' @return case的组成元素
Public Function createCaseCompositionElem(ByRef startToken As SqlToken) As SqlFormatterCaseCompositionElem
    Set createCaseCompositionElem = New SqlFormatterCaseCompositionElem
    createCaseCompositionElem.Init startToken
End Function

' ======================================================================
' 检查作用域
' ======================================================================

' 检查ON起始的作用域是不是ON_DUPLICATE作用域
'
' @param startToken case的组成元素的起始 token
' @return case的组成元素
Public Function isOnDuplicateEnv(ByRef tokenScanner As ObjArrayRefScanner) As Boolean
    ' 1. 标记扫描器 `mark`
    tokenScanner.mark
    ' 2. 将扫描器移动到下一个 token
    tokenScanner.toNext
    ' 3. 检查: 如果扫描器已经用完
    If Not tokenScanner.hasNext Then
        ' 3.1. 重置扫描器位置并清除标记: `resetAndClearMark`
        tokenScanner.resetAndClearMark
        ' 3.2. 退出处理，并返回 `false`
        ' return false
        Exit Function
    End If
    ' 4. 声明变量: `boolean result = false`
    '    这里直接使用 isOnDuplicateEnv

    ' 5. 从扫描器中获取一个 token
    Dim token As SqlToken
    Set token = tokenScanner.peek
    ' 6. 检查: 如果 token 的类型是 `SqlTokenType.KEY_DUPLICATE`，设置 `result = true`
    If token.tokenType = SqlTokenType.KEY_DUPLICATE Then
        isOnDuplicateEnv = True
    End If
    ' 7. 重置扫描器位置并清除标记: `resetAndClearMark`
    tokenScanner.resetAndClearMark
    ' 8. 退出处理，并返回 `result`
    ' return result
End Function

' ======================================================================
' 生成元素字符串时的方法
' ======================================================================

' 连接组成元素数组为一行字符串
'
' @param componentArray 元素数组 (IObjArrayRef<ISqlFormatterElem> )
' @param config 配置对象
' @param leftParenthesisCount 左边界括号数量
' @param rightParenthesisCount 右边界括号数量
' @return 连接后的一行字符串
Public Function createFormatSingleStr( _
    ByRef componentArray As IObjArrayRef, _
    ByRef config As SqlFormatterConfiguration, _
    ByRef leftParenthesisCount As Integer, _
    ByRef rightParenthesisCount As Integer) As String

    ' ==================================================
    ' 1. 初始化变量
    ' ==================================================
    ' 1. 结果字符串 `result = ""`
    '    这里直接使用 createFormatSingleStr
    ' 2. 临时元素 `ISqlFormatterElem component`
    Dim component As ISqlFormatterElem
    ' 3. 遍历索引 `long i = 0`
    Dim i As Long
    ' 4. 临时字符串 `String temp`
    Dim temp As String

    ' ==================================================
    ' 2. 处理左边界括号
    ' ==================================================
    ' 1. 检查: 如果 `leftParenthesisCount > 0`，则继续
    If leftParenthesisCount > 0 Then
        ' 2. 生成等量的空格，并保存到 `result += UtilsString.repeatLeftParenthesis(leftParenthesisCount)`
        createFormatSingleStr = createFormatSingleStr & UtilsString.repeatLeftParenthesis(leftParenthesisCount)

        ' 3. 处理注释
        ' 3.1. 检查: 如果元素数组的数量为 1，则继续
        If componentArray.Count = 1 Then
            ' 3.2. 获取第一个元素到 `component`
            Set component = componentArray.Item(0)
            ' 3.3. 检查: 如果元素是注释，则继续
            If component.isComment Then
                ' 3.4. 连接 `result += " " + component.toString(config)`
                createFormatSingleStr = createFormatSingleStr & " " & component.toString(config)
                ' 3.5. 设置 `i = 1`
                i = 1
            End If
        End If
    End If

    ' ==================================================
    ' 3. 遍历元素
    ' ==================================================
    ' 1. 从 `i = i` 开始到 `componentArray.MaxIndex` 遍历 `componentArray`
    For i = i To componentArray.MaxIndex
        ' 2. 获取第 i 个元素到 `component`
        Set component = componentArray.Item(i)
        ' 3. 获取这个元素的字符串: `component.toString(config)` 到 `temp`
        temp = component.toString(config)
        ' 4. 连接: `result` 和 `temp`，结果保存到 `result`
        createFormatSingleStr = SqlFormatterHelper.joinTwoElemStr(createFormatSingleStr, temp)
        ' 5. 回到 1 继续下一轮遍历
    Next

    ' ==================================================
    ' 4. 处理右边界括号
    ' ==================================================
    ' 1. 检查: 如果 `rightParenthesisCount > 0`，则继续
    If rightParenthesisCount > 0 Then
        ' 2. 生成等量的空格，并保存到 `result += UtilsString.repeatRightParenthesis(rightParenthesisCount)`
        createFormatSingleStr = createFormatSingleStr & UtilsString.repeatRightParenthesis(rightParenthesisCount)
    End If

    ' ==================================================
    ' 5. 退出处理，并返回 `result`
    ' ==================================================
    ' 直接返回 createFormatSingleStr
    ' return result
End Function

' 以每个元素独占一行的形式生成字符串集合
'
' @param componentArray 元素数组 (IObjArrayRef<ISqlFormatterElem>)
' @param config 配置对象
' @param leftParenthesisCount 左括号数量
' @param rightParenthesisCount 右括号数量
' @return IPrimitiveArrayRef<String>
Public Function createElemExclusiveRowStrArr( _
    ByRef componentArray As IObjArrayRef, _
    ByRef config As SqlFormatterConfiguration, _
    ByRef leftParenthesisCount As Integer, _
    ByRef rightParenthesisCount As Integer) As IPrimitiveArrayRef
    ' ==================================================
    ' 1. 初始化变量
    ' ==================================================
    ' 1. 前缀空格 `String prefixSpaceStr;`
    Dim prefixSpaceStr As String
    ' 2. 结果集合
    Set createElemExclusiveRowStrArr = UtilsArrayRef.createRealPrimitiveArrayRef(8)

    ' ==================================================
    ' 2. 处理前缀空格
    ' ==================================================
    ' 1. 检查: 如果当前列对象存在边界括号，则继续
    If leftParenthesisCount > 0 Then
        ' 2. 生成一个单位的前缀空格 `prefixSpaceStr = config.elemNestLevelPrefixSpace`
        prefixSpaceStr = config.elemNestLevelPrefixSpace
    ElseIf rightParenthesisCount > 0 Then
        ' 2. 生成一个单位的前缀空格 `prefixSpaceStr = config.elemNestLevelPrefixSpace`
        prefixSpaceStr = config.elemNestLevelPrefixSpace
    End If

    ' ==================================================
    ' 3. 处理左边界括号
    ' ==================================================
    ' 1. 如果存在左边界括号，则继续: `leftParenthesisCount > 0`
    If leftParenthesisCount > 0 Then
        ' 2. 生成等量的左括号，并保存到 `result`
        createElemExclusiveRowStrArr.Add UtilsString.repeatLeftParenthesis(leftParenthesisCount)
    End If

    ' ==================================================
    ' 4. 连接元素数组
    ' ==================================================
    fullElemExclusiveRowStrArr createElemExclusiveRowStrArr, componentArray, config, prefixSpaceStr

    ' ==================================================
    ' 5. 处理右边界括号
    ' ==================================================
    ' 1. 如果存在右边界括号，则继续: `rightParenthesisCount > 0`
    If rightParenthesisCount > 0 Then
        ' 2. 生成等量的右括号，并保存到 `result`
        createElemExclusiveRowStrArr.Add UtilsString.repeatRightParenthesis(rightParenthesisCount)
    End If

    ' ==================================================
    ' 6. 退出处理，返回 `result`
    ' ==================================================
    ' return result
End Function

' 以每个元素独占一行的形式填充字符串集合
'
' @param result 用来保存连接结果的数组 (IPrimitiveArrayRef<String>)
' @param componentArray 元素数组 (IObjArrayRef<ISqlFormatterElem>)
' @param config 配置对象
' @param prefixSpaceStr 前缀空格
Public Sub fullElemExclusiveRowStrArr( _
    ByRef result As IPrimitiveArrayRef, _
    ByRef componentArray As IObjArrayRef, _
    ByRef config As SqlFormatterConfiguration, _
    ByRef prefixSpaceStr As String)
    ' ==================================================
    ' 1. 初始化变量
    ' ==================================================
    ' 1. 临时元素 `ISqlFormatterElem component`
    Dim component As ISqlFormatterElem
    ' 2. 遍历索引 `long i = 0`
    Dim i As Long
    ' 3. 元素的字符串集合 `IPrimitiveArrayRef<String> tempArray`
    Dim tempArray As IPrimitiveArrayRef
    ' 4. 上一个元素是不是以注释结尾 `boolean lastElemIsEndsWithComment = false`
    Dim lastElemIsEndsWithComment As Boolean

    ' ==================================================
    ' 2. 处理开头的同行注释
    ' ==================================================
    ' 1. 检查: 如果结果集合不是空，则继续: `result.Count > 0`
    If result.Count > 0 Then
        ' 2. 检查: 如果元素数组不是空，则继续: `componentArray.Count > 0`
        If componentArray.Count > 0 Then
            ' 3. 获取第一个元素，保存到 `component`
            Set component = componentArray.Item(0)
            ' 4. 检查: 如果元素类型是同行注释，则继续: `component.isShareLineComment() == true`
            If component.isShareLineComment Then
                ' 5. 将 `result 的最后一行 + " " + component.toString(config)` 重新保存到 `result` 最后一行
                '    这里不需要再附加前缀空格，相当于连接到了前一行上
                result.setVal result.MaxIndex, result.getLast & " " & component.toString(config)
                ' 6. 设置 `i = 1`
                i = 1
            End If
        End If
    End If

    ' ==================================================
    ' 3. 遍历组成元素
    ' ==================================================
    ' 1. 从 `i = i` 开始到 `componentArray.MaxIndex` 为止，遍历 `componentArray`
    For i = i To componentArray.MaxIndex
        ' 2. 获取第 i 个元素到 `component`
        Set component = componentArray.Item(i)

        ' ==============================
        ' 2.3. 检查: 如果 `component` 是和前一个元素处于同一行的注释
        ' ==============================
        If component.isShareLineComment Then
            ' 2.3.1. 检查: 如果前一个元素是以注释结尾的，将 `prefixSpaceStr + component.toString(config)` 保存到 `result`
            If lastElemIsEndsWithComment Then
                result.Add prefixSpaceStr & component.toString(config)

            ' 2.3.2. 否则，检查: 如果结果集合中有数据了 `result.Count > 0`，将 `result 的最后一行 + " " + component.toString(config)` 重新保存到最后一行
            '        在这里，前面不需要再附加一次 `prefixSpaceStr`，因为上一次保存最后一行时已经加过了
            ElseIf result.Count > 0 Then
                result.setVal result.MaxIndex, result.getLast & " " & component.toString(config)

            ' 2.3.3. 否则，将 `prefixSpaceStr + component.toString(config)` 保存到 `result`
            Else
                result.Add prefixSpaceStr & component.toString(config)
            End If

        ' ==============================
        ' 2.4 否则，检查，如果元素是单行(单行元素，或者单独一行的注释)
        ' ==============================
        ElseIf component.isMultiLine = False Then
            ' 1. 将单行字符串保存到 `result`: `prefixSpaceStr + component.toString(config)`
            result.Add prefixSpaceStr & component.toString(config)

        ' ==============================
        ' 2.5. 否则 (是多行的)
        ' ==============================
        Else
            ' 2.5.1. 获取元素的字符串集合 `tempArray = component.toStringArray(config)`
            Set tempArray = component.toStringArray(config)
            ' 2.5.2. 将 `tempArray` 合并到 `result`
            UtilsArrayRef.mergeArrayRefWithPrefix result, tempArray, prefixSpaceStr
        End If

        ' ==============================
        ' 2.6. 保存当前元素是不是以注释结尾的状态，到 `lastElemIsEndsWithComment`
        ' ==============================
        lastElemIsEndsWithComment = component.isEndsWithComment

        ' ==============================
        ' 2.6. 回到 1，进入下一轮遍历
        ' ==============================
    Next
End Sub

' 以连续连接元素的方式生成字符串集合
'
' @param componentArray 元素数组 (IObjArrayRef<ISqlFormatterElem>)
' @param config 配置对象
' @param leftParenthesisCount 左边界括号数量
' @param rightParenthesisCount 右边界括号数量
' @return 多行字符串集合 IPrimitiveArrayRef<String>
Public Function createElemContinuousStrArr( _
    ByRef componentArray As IObjArrayRef, _
    ByRef config As SqlFormatterConfiguration, _
    ByRef leftParenthesisCount As Integer, _
    ByRef rightParenthesisCount As Integer) As IPrimitiveArrayRef

    ' ==================================================
    ' 1. 初始化变量
    ' ==================================================
    ' 1. 前缀空格 `String prefixSpaceStr = ""`
    Dim prefixSpaceStr As String

    ' 2. 结果字符串集合 `IPrimitiveArrayRef<String> result = UtilsArrayRef.createRealPrimitiveArrayRef<String>(8);`
    Dim result As IPrimitiveArrayRef
    Set result = UtilsArrayRef.createRealPrimitiveArrayRef(8)
    ' 3. 临时元素 `ISqlFormatterElem component`
    Dim component As ISqlFormatterElem
    ' 4. 遍历索引 `long i = 0`
    Dim i As Long
    ' 5. 临时字符串 `String temp`
    Dim temp As String
    ' 6. 临时token `SqlToken token`
    Dim token As SqlToken
    ' 7. 行字符串 `String lineStr`
    Dim lineStr As String
    ' 8. 元素的字符串集合 `IPrimitiveArrayRef<String> tempArray`
    Dim tempArray As IPrimitiveArrayRef
    ' 9. 遍历索引 `long j`
    Dim j As Long

    ' ==================================================
    ' 2. 处理前缀空格
    ' ==================================================
    ' 1. 检查: 如果当前列对象存在边界括号，则继续
    If leftParenthesisCount > 0 Or rightParenthesisCount > 0 Then
        ' 2. 生成一个单位的前缀空格 `prefixSpaceStr = config.elemNestLevelPrefixSpace`
        prefixSpaceStr = config.elemNestLevelPrefixSpace
    End If

    ' ==================================================
    ' 3. 处理左边界括号
    ' ==================================================
    ' 1. 如果存在左边界括号，则继续: `leftParenthesisCount > 0`
    If leftParenthesisCount > 0 Then
        ' 2. 生成等量的左括号，并保存到 `lineStr`
        lineStr = UtilsString.repeatLeftParenthesis(leftParenthesisCount)
        ' 3. 尝试吸收后面出现的同行注释
        ' 3.1. 检查: 如果组成元素数量 > 0，则继续:
        If componentArray.Count > 0 Then
            ' 3.2. 获取第一个元素，保存到 `component`
            Set component = componentArray.Item(0)
            ' 3.3. 检查: 如果元素类型是同行注释，则继续: `component.isShareLineComment() == true`
            If component.isShareLineComment Then
                ' 3.4. 连接字符串 `lineStr += " " + component.toString(config)`
                lineStr = lineStr & " " & component.toString(config)
                ' 3.5. 设置 `i = 1`
                i = 1
            End If
        End If
        ' 4. 将 `lineStr` 保存到 `result`
        result.Add lineStr
        ' 5. 设置 `lineStr` 为空字符串
        lineStr = ""
    End If

    ' ==================================================
    ' 4. 遍历组成元素
    ' ==================================================
    ' 1. 从 `i = i` 开始到 `componentArray.MaxIndex` 为止，遍历 `componentArray`
    For i = i To componentArray.MaxIndex
        ' 2. 获取第 i 个元素到 `component`
        Set component = componentArray.Item(i)

        ' ==============================
        ' 4.3. 检查: 如果元素是注释
        ' ==============================
        If component.isComment Then
            ' 1. 检查: 如果元素是和前一个元素同一行的注释: `component.isShareLineComment() == true`
            If component.isShareLineComment Then
                ' 1.1. 检查: 如果 `lineStr` 不是空字符串
                If lineStr <> "" Then
                    ' 1.1.1. 将 `prefixSpaceStr + lineStr + " " + component.toString(config)` 保存到 `result`
                    result.Add prefixSpaceStr & lineStr & " " & component.toString(config)
                    ' 1.1.2. 重制 `lineStr` 为空字符串
                    lineStr = ""
                ' 1.2 否则，`lineStr` 是空字符串，表示这是第一个元素，或者前一个元素是注释结尾已经保存到结果集合了，所以这里直接保存即可
                Else
                    result.Add prefixSpaceStr & component.toString(config)
                End If

            ' 2. 否则 (元素是单独一行的注释)
            Else
                ' 2.1. 检查: 如果 `lineStr != ""`，则
                If lineStr <> "" Then
                    ' 2.1.1. 将 `prefixSpaceStr + lineStr` 保存到 `result`
                    result.Add prefixSpaceStr & lineStr
                    ' 2.1.2. 重置 `lineStr` 为空字符串
                    lineStr = ""
                End If
                ' 2.2. 保存注释 `prefixSpaceStr + component.toString(config)` 到 `result`
                result.Add prefixSpaceStr & component.toString(config)
            End If

        ' ==============================
        ' 4.4. 否则，检查，如果元素是单行: `component.isMultiLine() == false`
        ' ==============================
        ElseIf component.isMultiLine = False Then
            ' 1. 直接获取这个元素的单行字符串 `temp = component.toString(config)`
            temp = component.toString(config)
            ' 2. 连接 `lineStr` 和 `temp`，结果保存到 `lineStr`
            lineStr = SqlFormatterHelper.joinTwoElemStr(lineStr, temp)
            ' 3. 检查: 如果当前元素是以注释结尾的 `component.isEndsWithComment() == true`
            If component.isEndsWithComment Then
                ' 3.1. 将 `prefixSpaceStr + lineStr` 保存到 `result`
                result.Add prefixSpaceStr & lineStr
                ' 3.2. 设置 `lineStr` 为空字符串
                lineStr = ""
            End If

        ' ==============================
        ' 4.5. 否则 (是多行的)
        ' ==============================
        Else
            ' 1. 获取元素的字符串集合 `tempArray = component.toStringArray(config)`
            Set tempArray = component.toStringArray(config)

            ' 2. 处理第一行
            ' 2.1. 连接 `lineStr` + `tempArray` 的第一行，保存到 `lineStr`
            lineStr = SqlFormatterHelper.joinTwoElemStr(lineStr, tempArray.Item(0))
            ' 2.2. 将 `prefixSpaceStr + lineStr` 保存到 `result`
            result.Add prefixSpaceStr & lineStr

            ' 3. 从 `j = 1` 到 `tempArray.MaxIndex - 1` 遍历 `tempArray`
            For j = 1 To tempArray.MaxIndex - 1
                ' 3.1. 将 `prefixSpaceStr + tempArray 的每一行` 保存到 `result`
                result.Add prefixSpaceStr & tempArray.Item(j)
            Next

            ' 4. 设置 `lineStr = tempArray 的最后一行`
            lineStr = tempArray.getLast

            ' 6. 检查: 如果当前元素是以注释结尾的 `component.isEndsWithComment() == true`
            If component.isEndsWithComment Then
                ' 6.1. 将 `prefixSpaceStr + lineStr` 保存到 `result`
                result.Add prefixSpaceStr & lineStr
                ' 6.2. 设置 `lineStr` 为空字符串
                lineStr = ""
            End If
        End If

        ' ==============================
        ' 4.6. 回到 1，进入下一轮遍历
        ' ==============================
    Next

    ' ==================================================
    ' 5. 处理最后一行
    ' ==================================================
    ' 1. 检查: 如果 `lineStr != ""`，则继续
    If lineStr <> "" Then
        ' 2. 将 `prefixSpaceStr + lineStr` 保存到 `result`
        result.Add prefixSpaceStr & lineStr
    End If

    ' ==================================================
    ' 6. 处理右边界括号
    ' ==================================================
    ' 1. 如果存在右边界括号，则继续: `rightParenthesisCount > 0`
    If rightParenthesisCount > 0 Then
        ' 2. 生成等量的右括号，并保存到 `result`
        result.Add UtilsString.repeatRightParenthesis(rightParenthesisCount)
    End If

    ' ==================================================
    ' 7. 退出处理，并返回 `result`
    ' ==================================================
    ' return result
    Set createElemContinuousStrArr = result
End Function

' 以连续连接和逗号换行的方式生成字符串集合
'
' @param componentArray 元素数组 (IObjArrayRef<ISqlFormatterElem>)
' @param config 配置对象
' @param leftParenthesisCount 左边界括号数量
' @param rightParenthesisCount 右边界括号数量
' @return 多行字符串集合 IPrimitiveArrayRef<String>
Public Function createElemContinuousAndDelimitByCommaStrArr( _
    ByRef componentArray As IObjArrayRef, _
    ByRef config As SqlFormatterConfiguration, _
    ByRef leftParenthesisCount As Integer, _
    ByRef rightParenthesisCount As Integer) As IPrimitiveArrayRef

    ' ==================================================
    ' 1. 初始化变量
    ' ==================================================
    ' 1. 前缀空格 `String prefixSpaceStr = ""`
    Dim prefixSpaceStr As String
    ' 2. 结果字符串集合 `IPrimitiveArrayRef<String> result = UtilsArrayRef.createRealPrimitiveArrayRef<ISqlFormatterElem>(8)`
    Dim result As IPrimitiveArrayRef
    Set result = UtilsArrayRef.createRealPrimitiveArrayRef(8)
    ' 3. 临时元素 `ISqlFormatterElem component`
    Dim component As ISqlFormatterElem
    ' 4. 遍历索引 `long i = 0`
    Dim i As Long
    ' 5. 临时字符串 `String temp`
    Dim temp As String
    ' 6. 临时token `SqlToken token`
    Dim token As SqlToken
    ' 7. 行字符串 `String lineStr`
    Dim lineStr As String
    ' 8. 元素的字符串集合 `IPrimitiveArrayRef<String> tempArray`
    Dim tempArray As IPrimitiveArrayRef
    ' 9. 遍历索引 `long j`
    Dim j As long
    ' 10. 数组的最大索引 `long componentArrMaxIndex`
    Dim componentArrMaxIndex As Long

    ' ==================================================
    ' 2. 处理前缀空格
    ' ==================================================
    ' 1. 检查: 如果当前列对象存在边界括号，则继续
    If leftParenthesisCount > 0 Or rightParenthesisCount > 0 Then
        ' 2. 生成一个单位的前缀空格 `prefixSpaceStr = config.elemNestLevelPrefixSpace`
        prefixSpaceStr = config.elemNestLevelPrefixSpace
    End If

    ' ==================================================
    ' 3. 处理左边界括号
    ' ==================================================
    ' 1. 如果存在左边界括号，则继续: `leftParenthesisCount > 0`
    If leftParenthesisCount > 0 Then
        ' 2. 生成等量的左括号，并保存到 `lineStr`
        lineStr = UtilsString.repeatLeftParenthesis(leftParenthesisCount)
        ' 3. 尝试吸收后面出现的同行注释
        ' 3.1. 检查: 如果组成元素数量 > 0，则继续:
        If componentArray.Count > 0 Then
            ' 3.2. 获取第一个元素，保存到 `component`
            Set component = componentArray.Item(0)
            ' 3.3. 检查: 如果元素类型是同行注释，则继续: `component.isShareLineComment() == true`
            If component.isShareLineComment Then
                ' 3.4. 连接字符串 `lineStr += " " + component.toString(config)`
                lineStr = lineStr & " " & component.toString(config)
                ' 3.5. 设置 `i = 1`
                i = 1
            End If
        End If
        ' 4. 将 `lineStr` 保存到 `result`
        result.Add lineStr
        ' 5. 设置 `lineStr` 为空字符串
        lineStr = ""
    End If

    ' ==================================================
    ' 4. 遍历组成元素
    ' ==================================================
    ' 1. 保存元素数组的最大索引 `componentArrMaxIndex = componentArray.MaxIndex`
    componentArrMaxIndex = componentArray.MaxIndex

    ' 2. 从 `i = i` 开始到 `componentArray.MaxIndex` 为止，遍历 `componentArray`
    For i = i To componentArrMaxIndex
        ' 3. 获取第 i 个元素到 `component`
        Set component = componentArray.Item(i)

        ' ==============================
        ' 4.4. 检查: 如果元素是注释
        ' ==============================
        If component.isComment Then
            ' 1. 检查: 如果元素是和前一个元素同一行的注释: `component.isShareLineComment() == true`
            If component.isShareLineComment Then
                ' 1.1. 检查: 如果 `lineStr` 不是空字符串，设置 `lineStr += " "`
                If lineStr <> "" Then
                    lineStr = lineStr & " "

                ' 1.2. 否则，检查: 如果结果集合中有数据了 `result.Count > 0`，则弹出最后一行，并设置 `lineStr += result.pollLast() + " "`
                ElseIf result.Count > 0 Then
                    lineStr = lineStr & result.pollLast & " "
                End If

                ' 1.3. 将 `prefixSpaceStr + lineStr + component.toString(config)` 保存到 `result`
                result.Add prefixSpaceStr & lineStr & component.toString(config)

                ' 1.4. 重制 `lineStr` 为空字符串
                lineStr = ""

            ' 2. 否则 (元素是单独一行的注释)
            Else
                ' 2.1. 检查: 如果 `lineStr != ""`，则
                If lineStr <> "" Then
                    ' 2.1.1. 将 `prefixSpaceStr + lineStr` 保存到 `result`
                    result.Add prefixSpaceStr & lineStr
                    ' 2.1.2. 重置 `lineStr` 为空字符串
                    lineStr = ""
                End If
                ' 2.2. 保存注释 `prefixSpaceStr + component.toString(config)` 到 `result`
                result.Add prefixSpaceStr & component.toString(config)
            End If

        ' ==============================
        ' 4.4. 否则，检查，如果元素是单行: `component.isMultiLine() == false`
        ' ==============================
        ElseIf component.isMultiLine = False Then
            ' 1. 直接获取这个元素的单行字符串 `temp = component.toString(config)`
            temp = component.toString(config)
            ' 2. 连接 `lineStr` 和 `temp`，结果保存到 `lineStr`
            lineStr = SqlFormatterHelper.joinTwoElemStr(lineStr, temp)
            ' 3. 检查: 如果元素是逗号: `temp == ","`
            If temp = "," Then
                ' 3.1. 检查下一个是不是和前一个元素同一行的注释
                ' 3.1.1. 检查: 如果 `i < componentArrMaxIndex`，则继续
                If i < componentArrMaxIndex Then
                    ' 3.1.2. 获取第 `i + 1` 个元素到 `component`
                    Set component = componentArray.Item(i + 1)
                    ' 3.1.3. 检查: 如果元素是和前一个元素同一行的注释，则继续: `component.isShareLineComment() == true`
                    If component.isShareLineComment Then
                        ' 3.1.4. 获取这个元素的单行字符串 `temp = component.toString(config)`
                        temp = component.toString(config)
                        ' 3.1.5. 连接: `lineStr + " " + temp`，结果保存到 `lineStr`
                        lineStr = lineStr & " " & temp
                        ' 3.1.6. i++
                        i = i + 1
                    End If
                End If

                ' 3.2. 将 `prefixSpaceStr + lineStr` 保存到 `result`
                result.Add prefixSpaceStr & lineStr
                ' 3.3. 重制 `lineStr` 为空字符串
                lineStr = ""

            ' 4. 否则，检查: 如果当前元素是以注释结尾的 `component.isEndsWithComment() == true`
            ElseIf component.isEndsWithComment Then
                ' 4.1. 将 `prefixSpaceStr + lineStr` 保存到 `result`
                result.Add prefixSpaceStr & lineStr
                ' 4.2. 设置 `lineStr` 为空字符串
                lineStr = ""
            End If

        ' ==============================
        ' 4.5. 否则 (是多行的)
        ' ==============================
        Else
            ' 1. 获取元素的字符串集合 `tempArray = component.toStringArray(config)`
            Set tempArray = component.toStringArray(config)

            ' 2. 处理第一行
            ' 2.1. 连接 `lineStr` + `tempArray` 的第一行，保存到 `lineStr`
            lineStr = SqlFormatterHelper.joinTwoElemStr(lineStr, tempArray.Item(0))
            ' 2.2. 将 `prefixSpaceStr + lineStr` 保存到 `result`
            result.Add prefixSpaceStr & lineStr

            ' 3. 从 `j = 1` 到 `tempArray.MaxIndex - 1` 遍历 `tempArray`
            For j = 1 To tempArray.MaxIndex - 1
                ' 3.1. 将 `prefixSpaceStr + tempArray 的每一行` 保存到 `result`
                result.Add prefixSpaceStr & tempArray.Item(j)
            Next

            ' 4. 设置 `lineStr = tempArray 的最后一行`
            lineStr = tempArray.getLast

            ' 5. 检查: 如果当前元素是以注释结尾的 `component.isEndsWithComment() == true`
            If component.isEndsWithComment Then
                ' 5.1. 将 `prefixSpaceStr + lineStr` 保存到 `result`
                result.Add prefixSpaceStr & lineStr
                ' 5.2. 设置 `lineStr` 为空字符串
                lineStr = ""
            End If
        End If
    Next

    ' ==================================================
    ' 5. 处理最后一行
    ' ==================================================
    ' 1. 检查: 如果 `lineStr != ""`，则继续
    If lineStr <> "" Then
        ' 2. 将 `prefixSpaceStr + lineStr` 保存到 `result`
        result.Add prefixSpaceStr & lineStr
    End If

    ' ==================================================
    ' 6. 处理右边界括号
    ' ==================================================
    ' 1. 如果存在右边界括号，则继续: `rightParenthesisCount > 0`
    If rightParenthesisCount > 0 Then
        ' 2. 生成等量的右括号，并保存到 `result`
        result.Add UtilsString.repeatRightParenthesis(rightParenthesisCount)
    End If

    ' ==================================================
    ' 7. 退出处理，并返回 `result`
    ' ==================================================
    ' return result
    Set createElemContinuousAndDelimitByCommaStrArr = result
End Function

' 连接对齐元素中的组成元素并填充到结果集合
' @param config 配置对象
' @param componentArray 元素数组 (IObjArrayRef<ISqlFormatterElem>)
' @param resultArray 保存结果的容器 (IPrimitiveArrayRef<String>)
' @param startPrefixSpace 起始的前缀空格
' @param startStr 起始字符串
' @param extraSpaceAfterFirstSave 在发生第一次换行后，需要附加的额外的前缀空格
Public Sub fullAndJoinArrayElem( _
    ByRef config As SqlFormatterConfiguration, _
    ByRef componentArray As IObjArrayRef, _
    ByRef resultArray As IPrimitiveArrayRef, _
    ByRef startPrefixSpace As String, _
    ByRef startStr As String, _
    ByRef extraSpaceAfterFirstSave As String)

    ' ==================================================
    ' 1. 声明变量
    ' ==================================================
    ' 1. 组成元素 `ISqlFormatterElem component`
    Dim component As ISqlFormatterElem
    ' 2. 一个元素的字符串 `String componentStr`
    Dim componentStr As String
    ' 3. 一个元素的多行字符串 `IPrimitiveArrayRef<String> componentMultiLine`
    Dim componentMultiLine As IPrimitiveArrayRef
    ' 4. 遍历的索引 `long i`
    Dim i As Long
    ' 5. 是否已经执行过保存了 `boolean hasBeenSaved = false`
    Dim hasBeenSaved As Boolean
    ' 6. 前缀空格 `String prefixSpace = startPrefixSpace`
    Dim prefixSpace As String
    prefixSpace = startPrefixSpace
    ' 7. 单行字符串 `String lineStr = startStr`
    Dim lineStr As String
    lineStr = startStr
    ' 8. 遍历的索引 `long j`
    Dim j As long

    ' ==================================================
    ' 2. 遍历元素并进行连接
    ' ==================================================
    ' 2.1. 从 `i = 0` 开始到 `componentArray.MaxIndex` 为止，遍历 `componentArray`
    For i = 0 To componentArray.MaxIndex
        ' 2.2. 从 `componentArray` 获取第 `i` 个元素，保存到 `component`
        Set component = componentArray.Item(i)

        ' ==============================
        ' 2.3. 检查: 如果 `component` 是注释 token
        ' ==============================
        If component.isComment Then
            ' 1. 检查: 如果是单行注释 `component.getType() == SqlTokenType.SINGLE_LINE_COMMENT`
            If component.getType = SqlTokenType.SINGLE_LINE_COMMENT Then
                ' 1.1. 检查: 如果 `lineStr != ""`
                If lineStr <> "" Then
                    ' 1.1.1. 将 `prefixSpace + lineStr` 保存到 `resultArray`
                    resultArray.Add prefixSpace & lineStr
                    ' 1.1.2. 重新设置保存过一次后的前缀空格
                    ' 1.1.2.1. 检查: 如果是第一次保存 `hasBeenSaved == false`，则继续
                    If hasBeenSaved = False Then
                        ' 1.1.2.2. 设置 `hasBeenSaved = true`
                        hasBeenSaved = True
                        ' 1.1.2.3. 添加额外的前缀空格 `prefixSpace += extraSpaceAfterFirstSave`
                        prefixSpace = prefixSpace & extraSpaceAfterFirstSave
                    End If
                End If
                ' 1.2.将 `prefixSpace + 注释的value` 保存到 `resultArray`
                resultArray.Add prefixSpace & component.toString(config)

            ' 2. 否则 (`SqlTokenType.SHARE_LINE_COMMENT`)
            Else
                ' 2.1. 检查: 如果 `lineStr != ""`，将 `prefixSpace + lineStr + ' ' + 注释的value` 保存到 `resultArray`
                If lineStr <> "" Then
                    resultArray.Add prefixSpace & lineStr & " " & component.toString(config)

                ' 2. 否则，将 `prefixSpace + 注释的value` 保存到 `resultArray`
                Else
                    resultArray.Add prefixSpace & component.toString(config)
                End If
            End If

            ' 3. 重新设置保存过一次后的前缀空格
            ' 3.1. 检查: 如果是第一次保存 `hasBeenSaved == false`，则继续
            If hasBeenSaved = False Then
                ' 3.2. 设置 `hasBeenSaved = true`
                hasBeenSaved = True
                ' 3.3. 添加额外的前缀空格 `prefixSpace += extraSpaceAfterFirstSave`
                prefixSpace = prefixSpace & extraSpaceAfterFirstSave
            End If

            ' 4. 重置 `lineStr = ""`
            lineStr = ""

        ' ==============================
        ' 2.4. 否则，检查: 如果这个元素是单行的
        ' ==============================
        ElseIf component.isMultiLine = False Then
            ' 1. 生成元素的单行字符串
            componentStr = component.toString(config)

            ' 2. 连接 `lineStr` + `componentStr`, 结果保存到 `lineStr`
            lineStr = SqlFormatterHelper.joinTwoElemStr(lineStr, componentStr)

        ' ==============================
        ' 2.5. 否则 (元素是多行的)
        ' ==============================
        Else
            ' 1. 生成这个元素的多行字符串
            Set componentMultiLine = component.toStringArray(config)
            ' 2. 连接 `lineStr` + `componentMultiLine` 的第一行, 结果保存到 `lineStr`
            lineStr = SqlFormatterHelper.joinTwoElemStr(lineStr, componentMultiLine.Item(0))
            ' 3. 将 `prefixSpace` + `lineStr` 保存到 `resultArray`
            resultArray.Add prefixSpace & lineStr
            ' 4. 重新设置保存过一次后的前缀空格
            ' 4.1. 检查: 如果是第一次保存 `hasBeenSaved == false`，则继续
            If hasBeenSaved = false Then
                ' 4.2. 设置 `hasBeenSaved = true`
                hasBeenSaved = true
                ' 4.3. 额外增加一个单位的前缀空格 `prefixSpace += extraSpaceAfterFirstSave`
                prefixSpace = prefixSpace + extraSpaceAfterFirstSave
            End If

            ' 5. 从 j = 1 开始到 componentMultiLine.MaxIndex 遍历 `componentMultiLine`，将 `prefixSpace` + `componentMultiLine` 每一行，保存到 `resultArray`
            For j = 1 To componentMultiLine.MaxIndex
                resultArray.Add prefixSpace & componentMultiLine.Item(j)
            Next

            ' 6. 重置 `lineStr` 为空字符串
            lineStr = ""
        End If

        ' 6. 从 2 开始下一次遍历
    Next

    ' ==================================================
    ' 3. 检查: 如果 `lineStr` 不是空字符串，则将 `prefixSpace` + `lineStr` 保存到 `resultArray`
    ' ==================================================
    If lineStr <> "" Then
        resultArray.Add prefixSpace & lineStr
    End If
End Sub

' 通用方法--连接两个元素的字符串
'
' @param prev 第一个字符串
' @param cur 第二个字符串
' @return 连接后的字符串
Public Function joinTwoElemStr(ByRef prev As String, ByRef cur As String) As String
    ' ==================================================
    ' 1. 检查: 如果 `prev` 是空字符串 `prev == ""`，则返回 `cur`
    ' ==================================================
    If prev = "" Then
        joinTwoElemStr = cur
        Exit Function
    End If

    ' ==================================================
    ' 2. 处理逗号
    ' ==================================================
    Dim prevLastChar As String

    ' 1. 检查: 如果 `cur == ","`，则返回 `prev + cur`
    If cur = "," Then
        joinTwoElemStr = prev & cur
        Exit Function

    ElseIf cur = ")" Then
        joinTwoElemStr = prev & cur
        Exit Function
    End If

    ' ==================================================
    ' 3. 按照 `prev` 的最后字符来连接
    ' ==================================================
    ' 1. 获取 prev 的最后一个字符 `prevLastChar`
    prevLastChar = Right(prev, 1)

    Select Case prevLastChar
        Case " "
            ' 2. 检查: 如果 `prevLastChar == " "`，则返回 `prev + cur`
            joinTwoElemStr = prev & cur
            Exit Function
        Case "("
            ' 3. 检查: 如果 `prevLastChar == "("`
            ' 3.1. 检查: 如果 `cur` 的第一个字符是 `"-"`，则返回 `prev + " " + cur`
            If Left(cur, 1) = "-" Then
                joinTwoElemStr = prev + " " + cur

            ' 3.2. 否则，返回 `prev + cur`
            Else
                joinTwoElemStr = prev + cur
            End If
            Exit Function
        Case Else
            ' 4. 否则，返回 `prev + " " + cur`
            joinTwoElemStr = prev & " " & cur
            Exit Function
    End Select
End Function

' ======================================================================
' 计算元素的偏移量
' ======================================================================

' 获取左括号栈中下一个左括号的偏移量
'
' @param leftParenthesisStack 左括号栈 (ObjStack<SqlToken>)
' @return 下一个左括号的偏移量
Public Function getLeftParenthesisOffsetInStack(ByRef leftParenthesisStack As ObjStack) As Integer
    ' ==================================================
    ' 1. 声明变量
    ' ==================================================
    ' 1. 遍历索引 `int i`
    Dim i As Long
    ' 2. token `SqlToken token`
    Dim token As SqlToken
    ' 3. 偏移量 `int result`
    '    这里直接使用 getLeftParenthesisOffsetInStack

    ' ==================================================
    ' 2. 遍历左括号栈
    ' ==================================================
    With leftParenthesisStack ' -- 在这里加速
        ' 1. 从 `i = leftParenthesisStack.Count - 1` 开始到 0 倒序遍历 `leftParenthesisStack`
        For i = .Count - 1 To 0 Step -1
            ' 2. 从 `leftParenthesisStack` 中获取第 i 个 `token`
            Set token = .Item(i)
            ' 3. 增加偏移量 `result++`
            getLeftParenthesisOffsetInStack = getLeftParenthesisOffsetInStack + 1
            ' 4. 检查: 如果 token 的类型是左括号，则退出处理，并返回 `result`
            If token.tokenType = SqlTokenType.LEFT_PARENTHESIS Then
                ' return result
                Exit Function
            End If

            ' 5. 进入下一轮遍历
        Next
    End With
    ' ==================================================
    ' 3. 退出处理，并返回 0
    ' ==================================================
    ' return 0
    getLeftParenthesisOffsetInStack = 0
End Function

' 获取扫描器到下一个最近的逗号的偏移量
'
' @param tokenScanner 扫描器
' @return 扫描器到下一个最近的逗号的偏移量
'         如果遇到逗号和注释之外的元素，将会返回 0
Public Function getNextCommaOffset(ByRef tokenScanner As ObjArrayRefScanner) As Integer
    getNextCommaOffset = SqlFormatterHelper.getNextTargetElemOffset(tokenScanner, SqlTokenType.COMMA)
End Function

' 获取扫描器到下一个最近的左括号的偏移量
'
' @param tokenScanner 扫描器
' @return 扫描器到下一个最近的左括号的偏移量
'         如果遇到左括号和注释之外的元素，将会返回 0
Public Function getNextLeftParenthesisOffset(ByRef tokenScanner As ObjArrayRefScanner) As Integer
    getNextLeftParenthesisOffset = SqlFormatterHelper.getNextTargetElemOffset(tokenScanner, SqlTokenType.LEFT_PARENTHESIS)
End Function

' 获取扫描器到下一个最近的右括号的偏移量
'
' @param tokenScanner 扫描器
' @return 扫描器到下一个最近的右括号的偏移量
'         如果遇到右括号和注释之外的元素，将会返回 0
Public Function getNextRightParenthesisOffset(ByRef tokenScanner As ObjArrayRefScanner) As Integer
    getNextRightParenthesisOffset = SqlFormatterHelper.getNextTargetElemOffset(tokenScanner, SqlTokenType.RIGHT_PARENTHESIS)
End Function

' 获取扫描器到下一个最近的目标元素的偏移量
'
' @param tokenScanner 扫描器
' @param targetType 目标元素的类型
' @return 扫描器到下一个最近的目标类型元素的偏移量
'         如果遇到目标类型元素和注释之外的元素，将会返回 0
Public Function getNextTargetElemOffset( _
    ByRef tokenScanner As ObjArrayRefScanner, _
    ByRef targetType As SqlTokenType) As Integer

    ' ==================================================
    ' 1. 声明变量
    ' ==================================================
    ' 1. 偏移量 `int offset = 0`
    '    这里直接使用 getNextTargetElemOffset
    ' 2. token `SqlToken token`
    Dim token As SqlToken
    ' 3. tokenType `SqlTokenType tokenType`
    Dim tokenType As SqlTokenType

    With tokenScanner ' -- 在这里加速
        ' ==================================================
        ' 2. 标记扫描器
        ' ==================================================
        .mark

        ' ==================================================
        ' 3. 搜索目标元素
        ' ==================================================
        ' 1. 遍历扫描器，如果扫描器用完，则退出遍历
        While .hasNext
            ' 2. 从 `tokenScanner` 中获取一个 `token`，并获取类型到 `tokenType`
            Set token = .peek
            tokenType = token.tokenType

            ' 3. 检查: 如果 token 类型是目标类型元素: `targetType`
            If tokenType = targetType Then
                ' 3.1. 增加偏移量: `offset += 1`
                getNextTargetElemOffset = getNextTargetElemOffset + 1
                ' 3.2. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 3.3. 退出处理，并返回 `offset`
                ' return offset
                Exit Function

            ' 4. 否则，检查: 如果 token 类型是注释: `SqlHelper.isCommentToken`
            ElseIf SqlHelper.isCommentToken(tokenType) Then
                ' 4.1. 增加偏移量: `offset += 1`
                getNextTargetElemOffset = getNextTargetElemOffset + 1
                ' 4.2. 扫描器向前推进
                .toNext

            ' 5. 否则
            Else
                ' 5.1. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 5.2. 退出处理，并返回 0
                ' return 0
                getNextTargetElemOffset = 0
                Exit Function
            End If
            ' 6. 从 2 开始下一轮遍历
        Wend

        ' ==================================================
        ' 4. 返回结果
        ' ==================================================
        ' 1. 重置扫描器位置并清除标记: `resetAndClearMark`
        .resetAndClearMark
        ' 2. 返回 0
        ' reutrn 0
        getNextTargetElemOffset = 0
    End With
End Function

' 获取扫描器到下一个最近的筛选行为关键字的偏移量
'
' @param tokenScanner 扫描器
' @return 扫描器到下一个最近的筛选行为关键字的偏移量
'         如果遇到目标类型元素和注释之外的元素，将会返回 0
Public Function getNextSelectStartActionSqlKeyOffset(ByRef tokenScanner As ObjArrayRefScanner) As Integer
    ' ==================================================
    ' 1. 声明变量
    ' ==================================================
    ' 1. 偏移量 `int offset = 0`
    '    这里直接使用 getNextSelectStartActionSqlKeyOffset
    ' 2. token `SqlToken token`
    Dim token As SqlToken
    ' 3. tokenType `SqlTokenType tokenType`
    Dim tokenType As SqlTokenType
    ' 4. 标记: 是否找到了目标运算符: `boolean findElem = false`
    Dim findElem As Boolean

    With tokenScanner ' -- 在这里加速
        ' ==================================================
        ' 2. 标记扫描器
        ' ==================================================
        .mark

        ' ==================================================
        ' 3. 搜索目标元素
        ' ==================================================
        ' 1. 遍历扫描器，如果扫描器用完，则退出遍历
        While .hasNext
            ' 2. 从 `tokenScanner` 中获取一个 `token`，并获取类型到 `tokenType`
            Set token = .peek
            tokenType = token.tokenType

            ' 3. 检查: 如果 token 类型是筛选行为关键字
            If SqlHelper.tokenTypeIsInSelectStartActionSqlKey(tokenType) Then
                ' 3.1. 增加偏移量: `offset += 1`
                getNextSelectStartActionSqlKeyOffset = getNextSelectStartActionSqlKeyOffset + 1
                ' 3.2. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 3.3. 退出处理，并返回 `offset`
                ' return offset
                Exit Function

            ' 4. 否则，检查: 如果 token 类型是注释: `SqlHelper.isCommentToken`
            ElseIf SqlHelper.isCommentToken(tokenType) Then
                ' 4.1. 增加偏移量: `offset += 1`
                getNextSelectStartActionSqlKeyOffset = getNextSelectStartActionSqlKeyOffset + 1
                ' 4.2. 扫描器向前推进
                .toNext

            ' 5. 否则
            Else
                ' 5.1. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 5.2. 退出处理，并返回 0
                ' return 0
                getNextSelectStartActionSqlKeyOffset = 0
                Exit Function
            End If
            ' 6. 从 2 开始下一轮遍历
        Wend

        ' ==================================================
        ' 4. 返回结果
        ' ==================================================
        ' 1. 重置扫描器位置并清除标记: `resetAndClearMark`
        .resetAndClearMark
        ' 2. 返回 0
        ' return 0
        getNextSelectStartActionSqlKeyOffset = 0
    End With
End Function

' 获取扫描器到下一个最近的不是使匹配筛选项操作停止的sql关键字的偏移量
'
' @param tokenScanner 扫描器
' @return 扫描器到下一个最近的使匹配筛选项操作停止的sql关键字的偏移量
'         如果遇到目标类型元素和注释之外的元素，将会返回 0
Public Function getNextNonSelectItemStopSqlKeyOffset(ByRef tokenScanner As ObjArrayRefScanner) As Integer
    ' ==================================================
    ' 1. 声明变量
    ' ==================================================
    ' 1. 偏移量 `int offset = 0`
    '    这里直接使用 getNextNonSelectItemStopSqlKeyOffset
    ' 2. token `SqlToken token`
    Dim token As SqlToken
    ' 3. tokenType `SqlTokenType tokenType`
    Dim tokenType As SqlTokenType

    With tokenScanner ' -- 在这里加速
        ' ==================================================
        ' 2. 标记扫描器
        ' ==================================================
        .mark

        ' ==================================================
        ' 3. 搜索目标元素
        ' ==================================================
        ' 1. 遍历扫描器，如果扫描器用完，则退出遍历
        While .hasNext
            ' 2. 从 `tokenScanner` 中获取一个 `token`，并获取类型到 `tokenType`
            Set token = .peek
            tokenType = token.tokenType

            ' 3. 检查: 如果 token 类型是注释: `SqlHelper.isCommentToken`
            If SqlHelper.isCommentToken(tokenType) Then
                ' 3.1. 增加偏移量: `offset += 1`
                getNextNonSelectItemStopSqlKeyOffset = getNextNonSelectItemStopSqlKeyOffset + 1
                ' 3.2. 扫描器向前推进
                .toNext

            ' 4. 否则，检查: 如果 token 类型不是使匹配筛选项操作停止的 sql 关键字集合
            ElseIf Not SqlHelper.tokenTypeIsSelectItemStopSqlKey(tokenType) Then
                ' 4.1. 增加偏移量: `offset += 1`
                getNextNonSelectItemStopSqlKeyOffset = getNextNonSelectItemStopSqlKeyOffset + 1
                ' 4.2. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 4.3. 退出处理，并返回 `offset`
                ' return offset
                Exit Function

            ' 5. 否则
            Else
                ' 5.1. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 5.2. 退出处理，并返回 `offset`
                ' return 0
                getNextNonSelectItemStopSqlKeyOffset = 0
                Exit Function
            End If
            ' 6. 从 2 开始下一轮遍历
        Wend

        ' ==================================================
        ' 4. 返回结果
        ' ==================================================
        ' 1. 重置扫描器位置并清除标记: `resetAndClearMark`
        .resetAndClearMark
        ' 2. 返回 0
        ' return 0
        getNextNonSelectItemStopSqlKeyOffset = 0
    End With
End Function

' 获取扫描器到下一个最近的不是sql各部分的起始关键字的偏移量
'
' @param tokenScanner 扫描器
' @return 扫描器到下一个最近的不是sql各部分的起始关键字的偏移量
'         如果遇到目标类型元素和注释之外的元素，将会返回 0
Public Function getNextNonSectionStartSqlKeyOffset(ByRef tokenScanner As ObjArrayRefScanner) As Integer
    ' ==================================================
    ' 1. 声明变量
    ' ==================================================
    ' 1. 偏移量 `int offset = 0`
    '    这里直接使用 getNextNonSectionStartSqlKeyOffset
    ' 2. token `SqlToken token`
    Dim token As SqlToken
    ' 3. tokenType `SqlTokenType tokenType`
    Dim tokenType As SqlTokenType

    With tokenScanner ' -- 在这里加速
        ' ==================================================
        ' 2. 标记扫描器
        ' ==================================================
        .mark

        ' ==================================================
        ' 3. 搜索目标元素
        ' ==================================================
        ' 1. 遍历扫描器，如果扫描器用完，则退出遍历
        While .hasNext
            ' 2. 从 `tokenScanner` 中获取一个 `token`，并获取类型到 `tokenType`
            Set token = .peek
            tokenType = token.tokenType

            ' 3. 检查: 如果 token 类型是注释: `SqlHelper.isCommentToken`
            If SqlHelper.isCommentToken(tokenType) Then
                ' 3.1. 增加偏移量: `offset += 1`
                getNextNonSectionStartSqlKeyOffset = getNextNonSectionStartSqlKeyOffset + 1
                ' 3.2. 扫描器向前推进
                .toNext

            ' 4. 否则，检查: 如果 token 类型不是使匹配筛选项操作停止的 sql 关键字集合
            ElseIf Not SqlHelper.tokenTypeIsSelectItemStopSqlKey(tokenType) Then
                ' 4.1. 增加偏移量: `offset += 1`
                getNextNonSectionStartSqlKeyOffset = getNextNonSectionStartSqlKeyOffset + 1
                ' 4.2. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 4.3. 退出处理，并返回 `offset`
                ' return offset
                Exit Function

            ' 5. 否则
            Else
                ' 5.1. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 5.2. 退出处理，并返回 `offset`
                ' return 0
                getNextNonSectionStartSqlKeyOffset = 0
                Exit Function
            End If
            ' 6. 从 2 开始下一轮遍历
        Wend

        ' ==================================================
        ' 4. 返回结果
        ' ==================================================
        ' 1. 重置扫描器位置并清除标记: `resetAndClearMark`
        .resetAndClearMark
        ' 2. 返回 0
        ' return 0
        getNextNonSectionStartSqlKeyOffset = 0
    End With
End Function

' 获取扫描器到下一个最近运算符的偏移量
'
' @param tokenScanner 扫描器
' @return 扫描器到下一个最近的运算符的偏移量
'         如果遇到目标类型元素和注释之外的元素，将会返回 0
Public Function getNextOperatorOffset(ByRef tokenScanner As ObjArrayRefScanner) As Integer
    ' ==================================================
    ' 1. 声明变量
    ' ==================================================
    ' 1. 偏移量 `int offset = 0`
    '    这里直接使用 getNextOperatorOffset
    ' 2. token `SqlToken token`
    Dim token As SqlToken
    ' 3. tokenType `SqlTokenType tokenType`
    Dim tokenType As SqlTokenType

    With tokenScanner ' -- 在这里加速
        ' ==================================================
        ' 2. 标记扫描器
        ' ==================================================
        .mark

        ' ==================================================
        ' 3. 搜索目标元素
        ' ==================================================
        ' 1. 遍历扫描器，如果扫描器用完，则退出遍历
        While .hasNext
            ' 2. 从 `tokenScanner` 中获取一个 `token`，并获取类型到 `tokenType`
            Set token = .peek
            tokenType = token.tokenType

            ' 3. 检查: 如果 token 类型是注释: `SqlHelper.isCommentToken`
            If SqlHelper.isCommentToken(tokenType) Then
                ' 3.1. 增加偏移量: `offset += 1`
                getNextOperatorOffset = getNextOperatorOffset + 1
                ' 3.2. 扫描器向前推进
                .toNext

            ' 4. 否则，检查: 如果 token 类型是运算符
            ElseIf SqlHelper.tokenTypeIsOperator(tokenType) Then
                ' 4.1. 增加偏移量: `offset += 1`
                getNextOperatorOffset = getNextOperatorOffset + 1
                ' 4.2. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 4.3. 退出处理，并返回 `offset`
                ' return offset
                Exit Function

            ' 5. 否则
            Else
                ' 5.1. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 5.2. 退出处理，并返回 `offset`
                ' return 0
                getNextOperatorOffset = 0
                Exit Function
            End If
            ' 6. 从 2 开始下一轮遍历
        Wend

        ' ==================================================
        ' 4. 返回结果
        ' ==================================================
        ' 1. 重置扫描器位置并清除标记: `resetAndClearMark`
        .resetAndClearMark
        ' 2. 返回 0
        ' return 0
        getNextOperatorOffset = 0
    End With
End Function

' 获取扫描器到下一个最近的算数运算符的偏移量
'
' @param tokenScanner 扫描器
' @return 扫描器到下一个最近的算数运算符的偏移量
'         如果遇到目标类型元素和注释之外的元素，将会返回 0
Public Function getNextComputeOperatorOffset(ByRef tokenScanner As ObjArrayRefScanner) As Integer
    ' ==================================================
    ' 1. 声明变量
    ' ==================================================
    ' 1. 偏移量 `int offset = 0`
    '    这里直接使用 getNextComputeOperatorOffset
    ' 2. token `SqlToken token`
    Dim token As SqlToken
    ' 3. tokenType `SqlTokenType tokenType`
    Dim tokenType As SqlTokenType

    With tokenScanner ' -- 在这里加速
        ' ==================================================
        ' 2. 标记扫描器
        ' ==================================================
        .mark

        ' ==================================================
        ' 3. 搜索目标元素
        ' ==================================================
        ' 1. 遍历扫描器，如果扫描器用完，则退出遍历
        While .hasNext
            ' 2. 从 `tokenScanner` 中获取一个 `token`，并获取类型到 `tokenType`
            Set token = .peek
            tokenType = token.tokenType

            ' 3. 检查: 如果 token 类型是注释: `SqlHelper.isCommentToken`
            If SqlHelper.isCommentToken(tokenType) Then
                ' 3.1. 增加偏移量: `offset += 1`
                getNextComputeOperatorOffset = getNextComputeOperatorOffset + 1
                ' 3.2. 扫描器向前推进
                .toNext

            ' 4. 否则，检查: 如果 token 类型是算数运算符
            ElseIf SqlHelper.tokenTypeIsComputeOperator(tokenType) Then
                ' 4.1. 增加偏移量: `offset += 1`
                getNextComputeOperatorOffset = getNextComputeOperatorOffset + 1
                ' 4.2. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 4.3. 退出处理，并返回 `offset`
                ' return offset
                Exit Function

            ' 5. 否则
            Else
                ' 5.1. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 5.2. 退出处理，并返回 `offset`
                ' return 0
                getNextComputeOperatorOffset = 0
                Exit Function
            End If
            ' 6. 从 2 开始下一轮遍历
        Wend

        ' ==================================================
        ' 4. 返回结果
        ' ==================================================
        ' 1. 重置扫描器位置并清除标记: `resetAndClearMark`
        .resetAndClearMark
        ' 2. 返回 0
        ' return 0
        getNextComputeOperatorOffset = 0
    End With
End Function

' 获取扫描器到下一个最近的条件运算符的偏移量 (为了兼容条件运算符前的 Not 运算符，这里也检查 Not)
'
' @param tokenScanner 扫描器
' @return 扫描器到下一个最近的算数运算符的偏移量
'         如果遇到目标类型元素和注释之外的元素，将会返回 0
Public Function getNextConditionOperatorOffset(ByRef tokenScanner As ObjArrayRefScanner) As Integer
    ' ==================================================
    ' 1. 声明变量
    ' ==================================================
    ' 1. 偏移量 `int offset = 0`
    '    这里直接使用 getNextConditionOperatorOffset
    ' 2. token `SqlToken token`
    Dim token As SqlToken
    ' 3. tokenType `SqlTokenType tokenType`
    Dim tokenType As SqlTokenType

    With tokenScanner ' -- 在这里加速
        ' ==================================================
        ' 2. 标记扫描器
        ' ==================================================
        .mark

        ' ==================================================
        ' 3. 搜索目标元素
        ' ==================================================
        ' 1. 遍历扫描器，如果扫描器用完，则退出遍历
        While .hasNext
            ' 2. 从 `tokenScanner` 中获取一个 `token`，并获取类型到 `tokenType`
            Set token = .peek
            tokenType = token.tokenType

            ' 3. 检查: 如果 token 类型是注释: `SqlHelper.isCommentToken`
            If SqlHelper.isCommentToken(tokenType) Then
                ' 3.1. 增加偏移量: `offset += 1`
                getNextConditionOperatorOffset = getNextConditionOperatorOffset + 1
                ' 3.2. 扫描器向前推进
                .toNext

            ' 4. 否则，检查: 如果 token 类型是条件运算符
            ElseIf SqlHelper.tokenTypeIsConditionOperator(tokenType) Then
                ' 4.1. 增加偏移量: `offset += 1`
                getNextConditionOperatorOffset = getNextConditionOperatorOffset + 1
                ' 4.2. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 4.3. 退出处理，并返回 `offset`
                ' return offset
                Exit Function

            ' 5. 否则，检查: 如果 token 类型是Not
            ElseIf tokenType = SqlTokenType.KEY_NOT Then
                ' 5.1. 增加偏移量: `offset += 1`
                getNextConditionOperatorOffset = getNextConditionOperatorOffset + 1
                ' 5.2. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 5.3. 退出处理，并返回 `offset`
                ' return offset
                Exit Function

            ' 6. 否则
            Else
                ' 6.1. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 6.2. 退出处理，并返回 `offset`
                ' return 0
                getNextConditionOperatorOffset = 0
                Exit Function
            End If
            ' 7. 从 2 开始下一轮遍历
        Wend

        ' ==================================================
        ' 4. 返回结果
        ' ==================================================
        ' 1. 重置扫描器位置并清除标记: `resetAndClearMark`
        .resetAndClearMark
        ' 2. 返回 0
        ' return 0
        getNextConditionOperatorOffset = 0
    End With
End Function

' 获取扫描器到下一个最近的表定义的偏移量
'
' @param tokenScanner 扫描器
' @return 扫描器到下一个最近的算数运算符的偏移量
'         如果遇到目标类型元素和注释之外的元素，将会返回 0
Public Function getNextTableFactorOffset(ByRef tokenScanner As ObjArrayRefScanner) As Integer
    ' ==================================================
    ' 1. 声明变量
    ' ==================================================
    ' 1. 偏移量 `int offset = 0`
    '    这里直接使用 getNextTableFactorOffset
    ' 2. token `SqlToken token`
    Dim token As SqlToken
    ' 3. tokenType `SqlTokenType tokenType`
    Dim tokenType As SqlTokenType

    With tokenScanner ' -- 在这里加速
        ' ==================================================
        ' 2. 标记扫描器
        ' ==================================================
        .mark

        ' ==================================================
        ' 3. 搜索目标元素
        ' ==================================================
        ' 1. 遍历扫描器，如果扫描器用完，则退出遍历
        While .hasNext
            ' 2. 从 `tokenScanner` 中获取一个 `token`，并获取类型到 `tokenType`
            Set token = .peek
            tokenType = token.tokenType

            ' 3. 检查: 如果 token 类型是注释: `SqlHelper.isCommentToken`
            If SqlHelper.isCommentToken(tokenType) Then
                ' 3.1. 增加偏移量: `offset += 1`
                getNextTableFactorOffset = getNextTableFactorOffset + 1
                ' 3.2. 扫描器向前推进
                .toNext
                ' 3.3. 从 2 开始下一轮遍历
                GoTo whileContinue

            ' 4. 否则，检查: 如果 token 类型是一个名称: `SqlTokenType.STR_NAME`，跳转到 9
            ' 5. 否则，检查: 如果 token 类型是with: `SqlTokenType.KEY_WITH`，跳转到 9
            ' 6. 否则，检查: 如果 token 类型是select: `SqlTokenType.KEY_SELECT`，跳转到 9
            ' 7. 否则，检查: 如果 token 类型是左括号: `SqlTokenType.LEFT_PARENTHESIS`，跳转到 9
            ElseIf tokenType = SqlTokenType.STR_NAME Then
            ElseIf tokenType = SqlTokenType.KEY_WITH Then
            ElseIf tokenType = SqlTokenType.KEY_SELECT Then
            ElseIf tokenType = SqlTokenType.LEFT_PARENTHESIS Then

            ' 8. 否则
            Else
                ' 6.1. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 6.2. 退出处理，并返回 0
                ' return 0
                getNextTableFactorOffset = 0
                Exit Function
            End If

            ' 9. 增加偏移量: `offset += 1`
            getNextTableFactorOffset = getNextTableFactorOffset + 1
            ' 10. 重置扫描器位置并清除标记: `resetAndClearMark`
            .resetAndClearMark
            ' 11. 退出处理，并返回 `offset`
            ' return offset
            Exit Function

    whileContinue:
        Wend
    whileBreak:

        ' ==================================================
        ' 4. 返回结果
        ' ==================================================
        ' 1. 重置扫描器位置并清除标记: `resetAndClearMark`
        .resetAndClearMark
        ' 2. 返回 0
        ' return 0
        getNextTableFactorOffset = 0
    End With
End Function

' 获取扫描器到下一个最近的index_hint起始关键字的偏移量
'
' @param tokenScanner 扫描器
' @return 扫描器到下一个最近的index_hint起始关键字的偏移量
'         如果遇到index_hint起始关键字和注释之外的元素，将会返回 0
Public Function getNextIndexHintStartKeyOffset(ByRef tokenScanner As ObjArrayRefScanner) As Integer
    ' ==================================================
    ' 1. 声明变量
    ' ==================================================
    ' 1. 偏移量 `int offset = 0`
    '     这里直接使用 getNextIndexHintStartKeyOffset

    ' 2. token `SqlToken token`
    Dim token As SqlToken
    ' 3. tokenType `SqlTokenType tokenType`
    Dim tokenType As SqlTokenType

    With tokenScanner ' -- 在这里加速
        ' ==================================================
        ' 2. 标记扫描器
        ' ==================================================
        .mark

        ' ==================================================
        ' 3. 搜索目标元素
        ' ==================================================
        ' 1. 遍历扫描器，如果扫描器用完，则退出遍历
        While .hasNext
            ' 2. 从 `tokenScanner` 中获取一个 `token`，并获取类型到 `tokenType`
            Set token = .peek
            tokenType = token.tokenType

            ' 3. 检查: 如果 token 类型是 USE: `SqlTokenType.KEY_USE`
            If tokenType = SqlTokenType.KEY_USE Then
                ' 3.1. 增加偏移量: `offset += 1`
                getNextIndexHintStartKeyOffset = getNextIndexHintStartKeyOffset + 1
                ' 3.2. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 3.3. 退出处理，并返回 `offset`
                ' return offset
                Exit Function

            ' 4. 检查: 如果 token 类型是 IGNORE: `SqlTokenType.KEY_IGNORE`
            ElseIf tokenType = SqlTokenType.KEY_IGNORE Then
                ' 4.1. 增加偏移量: `offset += 1`
                getNextIndexHintStartKeyOffset = getNextIndexHintStartKeyOffset + 1
                ' 4.2. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 4.3. 退出处理，并返回 `offset`
                ' return offset
                Exit Function

            ' 5. 检查: 如果 token 类型是 FORCE: `SqlTokenType.KEY_FORCE`
            ElseIf tokenType = SqlTokenType.KEY_FORCE Then
                ' 5.1. 增加偏移量: `offset += 1`
                getNextIndexHintStartKeyOffset = getNextIndexHintStartKeyOffset + 1
                ' 5.2. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 5.3. 退出处理，并返回 `offset`
                ' return offset
                Exit Function

            ' 6. 否则，检查: 如果 token 类型是注释: `SqlHelper.isCommentToken`
            ElseIf SqlHelper.isCommentToken(tokenType) Then
                ' 6.1. 增加偏移量: `offset += 1`
                getNextIndexHintStartKeyOffset = getNextIndexHintStartKeyOffset + 1
                ' 6.2. 扫描器向前推进
                .toNext

            ' 7. 否则
            Else
                ' 7.1. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 7.2. 退出处理，并返回 0
                ' return 0
                getNextIndexHintStartKeyOffset = 0
                Exit Function
            End If
            ' 8. 从 2 开始下一轮遍历
        Wend

        ' ==================================================
        ' 4. 返回结果
        ' ==================================================
        ' 1. 重置扫描器位置并清除标记: `resetAndClearMark`
        .resetAndClearMark
        ' 2. 返回 0
        ' return 0
        getNextIndexHintStartKeyOffset = 0
    End With
End Function

' 获取扫描器到下一个最近的with表定义的偏移量
'
' @param tokenScanner 扫描器
' @return 扫描器到下一个最近的with表定义的偏移量
'         如果遇到目标类型元素和注释之外的元素，将会返回 0
Public Function getNextWithTableOffset(ByRef tokenScanner As ObjArrayRefScanner) As Integer
    ' ==================================================
    ' 1. 声明变量
    ' ==================================================
    ' 1. 偏移量 `int offset = 0`
    '    这里直接使用 getNextWithTableOffset
    ' 2. token `SqlToken token`
    Dim token As SqlToken
    ' 3. tokenType `SqlTokenType tokenType`
    Dim tokenType As SqlTokenType

    With tokenScanner ' -- 在这里加速
        ' ==================================================
        ' 2. 标记扫描器
        ' ==================================================
        .mark

        ' ==================================================
        ' 3. 搜索目标元素
        ' ==================================================
        ' 1. 遍历扫描器，如果扫描器用完，则退出遍历
        While .hasNext
            ' 2. 从 `tokenScanner` 中获取一个 `token`，并获取类型到 `tokenType`
            Set token = .peek
            tokenType = token.tokenType

            ' 3. 检查: 如果 token 类型是注释: `SqlHelper.isCommentToken`
            If SqlHelper.isCommentToken(tokenType) Then
                ' 3.1. 增加偏移量: `offset += 1`
                getNextWithTableOffset = getNextWithTableOffset + 1
                ' 3.2. 扫描器向前推进
                .toNext
                ' 3.3. 从 2 开始下一轮遍历
                GoTo whileContinue

            ' 4. 否则，检查: 如果 token 类型是一个名称: `SqlTokenType.STR_NAME`，跳转到 9
            ' 5. 否则，检查: 如果 token 类型是 func: `SqlTokenType.STR_FUNC`，跳转到 7
            ElseIf tokenType = SqlTokenType.STR_NAME Then
            ElseIf tokenType = SqlTokenType.STR_FUNC Then

            ' 6. 否则
            Else
                ' 6.1. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 6.2. 退出处理，并返回 0
                ' return 0
                getNextWithTableOffset = 0
                Exit Function
            End If

            ' 7. 增加偏移量: `offset += 1`
            getNextWithTableOffset = getNextWithTableOffset + 1
            ' 8. 重置扫描器位置并清除标记: `resetAndClearMark`
            .resetAndClearMark
            ' 9. 退出处理，并返回 `offset`
            ' return offset
            Exit Function

    whileContinue:
        Wend
    whileBreak:

        ' ==================================================
        ' 4. 返回结果
        ' ==================================================
        ' 1. 重置扫描器位置并清除标记: `resetAndClearMark`
        .resetAndClearMark
        ' 2. 返回 0
        ' return 0
        getNextWithTableOffset = 0
    End With
End Function

' 获取扫描器到下一个最近的update行为关键字的偏移量
'
' @param tokenScanner 扫描器
' @return 扫描器到下一个最近的update行为关键字的偏移量
'         如果遇到目标类型元素和注释之外的元素，将会返回 0
Public Function getNextUpdateStartActionSqlKeyOffset(ByRef tokenScanner As ObjArrayRefScanner) As Integer
    ' ==================================================
    ' 1. 声明变量
    ' ==================================================
    ' 1. 偏移量 `int offset = 0`
    '    这里直接使用 getNextUpdateStartActionSqlKeyOffset
    ' 2. token `SqlToken token`
    Dim token As SqlToken
    ' 3. tokenType `SqlTokenType tokenType`
    Dim tokenType As SqlTokenType

    With tokenScanner ' -- 在这里加速
        ' ==================================================
        ' 2. 标记扫描器
        ' ==================================================
        .mark

        ' ==================================================
        ' 3. 搜索目标元素
        ' ==================================================
        ' 1. 遍历扫描器，如果扫描器用完，则退出遍历
        While .hasNext
            ' 2. 从 `tokenScanner` 中获取一个 `token`，并获取类型到 `tokenType`
            Set token = .peek
            tokenType = token.tokenType

            ' 3. 检查: 如果 token 类型是注释: `SqlHelper.isCommentToken`
            If SqlHelper.isCommentToken(tokenType) Then
                ' 3.1. 增加偏移量: `offset += 1`
                getNextUpdateStartActionSqlKeyOffset = getNextUpdateStartActionSqlKeyOffset + 1
                ' 3.2. 扫描器向前推进
                .toNext
                ' 3.3. 从 2 开始下一轮遍历
                GoTo whileContinue

            ' 4. 否则，检查: 如果 token 类型是 LOW_PRIORITY: `SqlTokenType.KEY_LOW_PRIORITY`，跳转到 7
            ' 5. 否则，检查: 如果 token 类型是 IGNORE: `SqlTokenType.KEY_IGNORE`，跳转到 7
            ElseIf tokenType = SqlTokenType.KEY_LOW_PRIORITY Then
            ElseIf tokenType = SqlTokenType.KEY_IGNORE Then

            ' 6. 否则
            Else
                ' 6.1. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 6.2. 退出处理，并返回 0
                ' return 0
                getNextUpdateStartActionSqlKeyOffset = 0
                Exit Function
            End If

            ' 7. 增加偏移量: `offset += 1`
            getNextUpdateStartActionSqlKeyOffset = getNextUpdateStartActionSqlKeyOffset + 1
            ' 8. 重置扫描器位置并清除标记: `resetAndClearMark`
            .resetAndClearMark
            ' 9. 退出处理，并返回 `offset`
            ' return offset
            Exit Function

    whileContinue:
        Wend
    whileBreak:

        ' ==================================================
        ' 4. 返回结果
        ' ==================================================
        ' 1. 重置扫描器位置并清除标记: `resetAndClearMark`
        .resetAndClearMark
        ' 2. 返回 0
        ' return 0
        getNextUpdateStartActionSqlKeyOffset = 0
    End With
End Function

' 获取扫描器到下一个最近的delete行为关键字的偏移量
'
' @param tokenScanner 扫描器
' @return 扫描器到下一个最近的delete行为关键字的偏移量
'         如果遇到目标类型元素和注释之外的元素，将会返回 0
Public Function getNextDeleteStartActionSqlKeyOffset(ByRef tokenScanner As ObjArrayRefScanner) As Integer
    ' ==================================================
    ' 1. 声明变量
    ' ==================================================
    ' 1. 偏移量 `int offset = 0`
    '    这里直接使用 getNextDeleteStartActionSqlKeyOffset
    ' 2. token `SqlToken token`
    Dim token As SqlToken
    ' 3. tokenType `SqlTokenType tokenType`
    Dim tokenType As SqlTokenType

    With tokenScanner ' -- 在这里加速
        ' ==================================================
        ' 2. 标记扫描器
        ' ==================================================
        .mark

        ' ==================================================
        ' 3. 搜索目标元素
        ' ==================================================
        ' 1. 遍历扫描器，如果扫描器用完，则退出遍历
        While .hasNext
            ' 2. 从 `tokenScanner` 中获取一个 `token`，并获取类型到 `tokenType`
            Set token = .peek
            tokenType = token.tokenType

            ' 3. 检查: 如果 token 类型是注释: `SqlHelper.isCommentToken`
            If SqlHelper.isCommentToken(tokenType) Then
                ' 3.1. 增加偏移量: `offset += 1`
                getNextDeleteStartActionSqlKeyOffset = getNextDeleteStartActionSqlKeyOffset + 1
                ' 3.2. 扫描器向前推进
                .toNext
                ' 3.3. 从 2 开始下一轮遍历
                GoTo whileContinue

            ' 4. 否则，检查: 如果 token 类型是 LOW_PRIORITY: `SqlTokenType.KEY_LOW_PRIORITY`，跳转到 8
            ' 5. 否则，检查: 如果 token 类型是 IGNORE: `SqlTokenType.KEY_IGNORE`，跳转到 8
            ' 6. 否则，检查: 如果 token 类型是 QUICK: `SqlTokenType.KEY_QUICK`，跳转到 8
            ElseIf tokenType = SqlTokenType.KEY_LOW_PRIORITY Then
            ElseIf tokenType = SqlTokenType.KEY_IGNORE Then
            ElseIf tokenType = SqlTokenType.KEY_QUICK Then

            ' 6. 否则
            Else
                ' 6.1. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 6.2. 退出处理，并返回 0
                ' return 0
                getNextDeleteStartActionSqlKeyOffset = 0
                Exit Function
            End If

            ' 7. 增加偏移量: `offset += 1`
            getNextDeleteStartActionSqlKeyOffset = getNextDeleteStartActionSqlKeyOffset + 1
            ' 8. 重置扫描器位置并清除标记: `resetAndClearMark`
            .resetAndClearMark
            ' 9. 退出处理，并返回 `offset`
            ' return offset
            Exit Function

    whileContinue:
        Wend
    whileBreak:

        ' ==================================================
        ' 4. 返回结果
        ' ==================================================
        ' 1. 重置扫描器位置并清除标记: `resetAndClearMark`
        .resetAndClearMark
        ' 2. 返回 0
        ' return 0
        getNextDeleteStartActionSqlKeyOffset = 0
    End With
End Function

' 获取扫描器到下一个最近的insert行为关键字的偏移量
'
' @param tokenScanner 扫描器
' @return 扫描器到下一个最近的insert行为关键字的偏移量
'         如果遇到目标类型元素和注释之外的元素，将会返回 0
Public Function getNextInsertStartActionSqlKeyOffset(ByRef tokenScanner As ObjArrayRefScanner) As Integer
    ' ==================================================
    ' 1. 声明变量
    ' ==================================================
    ' 1. 偏移量 `int offset = 0`
    '    这里直接使用 getNextInsertStartActionSqlKeyOffset
    ' 2. token `SqlToken token`
    Dim token As SqlToken
    ' 3. tokenType `SqlTokenType tokenType`
    Dim tokenType As SqlTokenType

    With tokenScanner ' -- 在这里加速
        ' ==================================================
        ' 2. 标记扫描器
        ' ==================================================
        .mark

        ' ==================================================
        ' 3. 搜索目标元素
        ' ==================================================
        ' 1. 遍历扫描器，如果扫描器用完，则退出遍历
        While .hasNext
            ' 2. 从 `tokenScanner` 中获取一个 `token`，并获取类型到 `tokenType`
            Set token = .peek
            tokenType = token.tokenType

            ' 3. 检查: 如果 token 类型是注释: `SqlHelper.isCommentToken`
            If SqlHelper.isCommentToken(tokenType) Then
                ' 3.1. 增加偏移量: `offset += 1`
                getNextInsertStartActionSqlKeyOffset = getNextInsertStartActionSqlKeyOffset + 1
                ' 3.2. 扫描器向前推进
                .toNext
                ' 3.3. 从 2 开始下一轮遍历
                GoTo whileContinue

            ' 4. 否则，检查: 如果 token 类型是 LOW_PRIORITY: `SqlTokenType.KEY_LOW_PRIORITY`，跳转到 9
            ' 5. 否则，检查: 如果 token 类型是 DELAYED: `SqlTokenType.KEY_DELAYED`，跳转到 9
            ' 6. 否则，检查: 如果 token 类型是 HIGH_PRIORITY: `SqlTokenType.KEY_HIGH_PRIORITY`，跳转到 9
            ' 7. 否则，检查: 如果 token 类型是 IGNORE: `SqlTokenType.KEY_IGNORE`，跳转到 9
            ElseIf tokenType = SqlTokenType.KEY_LOW_PRIORITY Then
            ElseIf tokenType = SqlTokenType.KEY_DELAYED Then
            ElseIf tokenType = SqlTokenType.KEY_HIGH_PRIORITY Then
            ElseIf tokenType = SqlTokenType.KEY_IGNORE Then

            ' 8. 否则
            Else
                ' 8.1. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 8.2. 退出处理，并返回 0
                ' return 0
                getNextInsertStartActionSqlKeyOffset = 0
                Exit Function
            End If

            ' 9. 增加偏移量: `offset += 1`
            getNextInsertStartActionSqlKeyOffset = getNextInsertStartActionSqlKeyOffset + 1
            ' 10. 重置扫描器位置并清除标记: `resetAndClearMark`
            .resetAndClearMark
            ' 11. 退出处理，并返回 `offset`
            ' return offset
            Exit Function

    whileContinue:
        Wend
    whileBreak:

        ' ==================================================
        ' 4. 返回结果
        ' ==================================================
        ' 1. 重置扫描器位置并清除标记: `resetAndClearMark`
        .resetAndClearMark
        ' 2. 返回 0
        ' return 0
        getNextInsertStartActionSqlKeyOffset = 0
    End With
End Function

' 获取扫描器到下一个rowAlias的偏移量
'
' @param tokenScanner 扫描器
' @return 扫描器到下一个rowAlias的偏移量
'         如果遇到目标类型元素和注释之外的元素，将会返回 0
Public Function getNextRowAliasOffset(ByRef tokenScanner As ObjArrayRefScanner) As Integer
    ' ==================================================
    ' 1. 声明变量
    ' ==================================================
    ' 1. 偏移量 `int offset = 0`
    '    这里直接使用 getNextRowAliasOffset
    ' 2. token `SqlToken token`
    Dim token As SqlToken
    ' 3. tokenType `SqlTokenType tokenType`
    Dim tokenType As SqlTokenType

    With tokenScanner ' -- 在这里加速
        ' ==================================================
        ' 2. 标记扫描器
        ' ==================================================
        .mark

        ' ==================================================
        ' 3. 搜索目标元素
        ' ==================================================
        ' 1. 遍历扫描器，如果扫描器用完，则退出遍历
        While .hasNext
            ' 2. 从 `tokenScanner` 中获取一个 `token`，并获取类型到 `tokenType`
            Set token = .peek
            tokenType = token.tokenType

            ' 3. 检查: 如果 token 类型是注释: `SqlHelper.isCommentToken`
            If SqlHelper.isCommentToken(tokenType) Then
                ' 3.1. 增加偏移量: `offset += 1`
                getNextRowAliasOffset = getNextRowAliasOffset + 1
                ' 3.2. 扫描器向前推进
                .toNext
                ' 3.3. 从 2 开始下一轮遍历
                GoTo whileContinue

            ' 4. 否则，检查: 如果 token 类型是 STR_NAME: `SqlTokenType.STR_NAME`，跳转到 7
            ' 5. 否则，检查: 如果 token 类型是 STR_FUNC: `SqlTokenType.STR_FUNC`，跳转到 7
            ElseIf tokenType = SqlTokenType.STR_NAME Then
            ElseIf tokenType = SqlTokenType.STR_FUNC Then

            ' 6. 否则
            Else
                ' 6.1. 重置扫描器位置并清除标记: `resetAndClearMark`
                .resetAndClearMark
                ' 6.2. 退出处理，并返回 0
                ' return 0
                getNextRowAliasOffset = 0
                Exit Function
            End If

            ' 7. 增加偏移量: `offset += 1`
            getNextRowAliasOffset = getNextRowAliasOffset + 1
            ' 8. 重置扫描器位置并清除标记: `resetAndClearMark`
            .resetAndClearMark
            ' 9. 退出处理，并返回 `offset`
            ' return offset
            Exit Function

    whileContinue:
        Wend
    whileBreak:

        ' ==================================================
        ' 4. 返回结果
        ' ==================================================
        ' 1. 重置扫描器位置并清除标记: `resetAndClearMark`
        .resetAndClearMark
        ' 2. 返回 0
        ' return 0
        getNextRowAliasOffset = 0
    End With
End Function